/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

   DummySyncSkeletonTest.cpp. Created on: 15.02.2010 16:51:41 
*/

#include "cpputils/TestUtils.hpp"
#include "smartobjects/SmartBus.hpp"
#include "smartobjects/network/EndpointLocation.hpp"
#include "smartobjects/core/ServerSkeleton.hpp"
#include "smartobjects/core/RemoteSmartObject.hpp"

using namespace smartobjects;

class DummySyncSkeleton : public virtual ServerSkeleton {
public:

	DummySyncSkeleton() {}
	virtual ~DummySyncSkeleton() {}

	inline virtual const std::string& getInterfaceId() const {
		const static std::string INTERFACE_ID = "DummySyncSkeleton";
		return INTERFACE_ID;
	}

	inline virtual const char** getSupportedInterfaces() const {
		const static char* SUPPORTED_INTERFACES[] = { "DummySyncSkeleton2", NULL };
		return SUPPORTED_INTERFACES;
	}

	virtual void onCallRequestReceived(
			ConnectionHandler* poConnectionHandler,
			CallInterfaceOperationRequest* poRequest) {
		std::string m_sCallInfoString;
		poRequest->getDataTypesCoder()->decodeString(m_sCallInfoString, poRequest->getBuffer());
		poRequest->getBuffer()->clear();
		boost::intrusive_ptr<CallInterfaceOperationResponse> poResponse (
				poRequest->createResponse(poRequest->getBuffer()), false );
		poRequest->setBuffer(NULL);
		poResponse->beginSerialize();
		poResponse->getDataTypesCoder()->encodeString(m_sCallInfoString, poResponse->getBuffer());
		poResponse->finishSerialize();
		sendResponse ( poConnectionHandler, poResponse.get() );
	}

};

class DummyRemoteSmartObject : public virtual RemoteSmartObject {
	std::string sLastResult;
	boost::recursive_mutex m_mxMonitor;
	boost::condition m_evAwait;

public:
	DummyRemoteSmartObject() {}
	virtual ~DummyRemoteSmartObject() {}

	inline virtual const std::string& getInterfaceId() const {
		const static std::string INTERFACE_ID = "DummySyncSkeleton";
		return INTERFACE_ID;
	}

	virtual void onCallResponseReceived(
			ConnectionHandler* poConnectionHandler,
			INetworkCommand* poRequest,
			CallInterfaceOperationResponse* poCallResponse,
			RefObject* poUserObject) {
		boost::unique_lock<boost::recursive_mutex> oLock(m_mxMonitor);
		poCallResponse->getDataTypesCoder()->decodeString(sLastResult, poCallResponse->getBuffer());
		m_evAwait.notify_one();
	}

	virtual void onResponseTimeout(
			INetworkCommand* poRequest,
			RefObject* poUserObject) {
	}

	const std::string& getLastResult() {
		return sLastResult;
	}

	boost::recursive_mutex& getMonitor() {
		return m_mxMonitor;
	}

	void waitForAnyEvent(boost::unique_lock<boost::recursive_mutex>& oLock, const std::string& sLocation = "Unknown location") {
		if(!m_evAwait.timed_wait(oLock, boost::posix_time::seconds(3))) {
			std::cout << "Timeout for waiting accept event! Location: " << sLocation <<std::endl;
			throw SystemException("Timeout for waiting accept event! Location: " + sLocation);
		}
	}
};

BOOST_AUTO_TEST_CASE ( TestDummySyncSkeletonTest ) {
	boost::intrusive_ptr<ISmartBus> poBus (new SmartBus(), false);

	boost::intrusive_ptr<URI> poServerURI ( new URI("tcp://localhost:3333"), false );

	boost::intrusive_ptr<IEndpointLocation> poServerEndpointLocation (
			new EndpointLocation( poBus.get(), poServerURI.get() ), false
	);

	for(unsigned int i=0; i<30; i++) {
		IServerSkeleton::ThreadModel nModel = IServerSkeleton::SINGLE_THREAD_MODEL;
		if(i%2 == 0) {
			nModel =  IServerSkeleton::FULL_MULTI_THREAD_MODEL;
		}
		else
		if(i%3 == 0) {
			nModel =  IServerSkeleton::PER_CLIENT_MULTI_THREAD_MODEL;
		}

		boost::intrusive_ptr<DummySyncSkeleton> poDummySkeleton (new DummySyncSkeleton(), false);
		poDummySkeleton->setThreadModel(nModel);

		poDummySkeleton->publish( poBus.get(), poServerEndpointLocation.get() );

		BOOST_REQUIRE( poDummySkeleton->isPublished() );

		boost::intrusive_ptr<DummyRemoteSmartObject> poDummyRemoteSmartObject ( new DummyRemoteSmartObject(), false );

		poDummyRemoteSmartObject->resolve(poBus.get(), poServerEndpointLocation.get());

		BOOST_REQUIRE( poDummyRemoteSmartObject->isAvailable() );

		boost::intrusive_ptr<CallInterfaceOperationRequest> poCallRequest (
				poDummyRemoteSmartObject->createCallRequest("DummyOperation",NULL), false );

		poCallRequest->getDataTypesCoder()->encodeString("Hello", poCallRequest->getBuffer());

		boost::intrusive_ptr<ConnectionHandler> poConnectionHandler ( poDummyRemoteSmartObject->getConnectionHandlerToSend(), false );
		boost::intrusive_ptr<CallInterfaceOperationResponse> poCallResponse ( poDummyRemoteSmartObject->call( poConnectionHandler.get(), poCallRequest.get() ), false );

		std::string sHelloResult;
		poCallResponse->getDataTypesCoder()->decodeString(sHelloResult, poCallResponse->getBuffer());
		BOOST_REQUIRE ( sHelloResult == "Hello" );

		poCallRequest.reset( poDummyRemoteSmartObject->createCallRequest("DummyOperation",NULL) );
		poCallRequest->release();
		poCallRequest->getDataTypesCoder()->encodeString("Hello", poCallRequest->getBuffer());

		boost::unique_lock<boost::recursive_mutex> oLock(poDummyRemoteSmartObject->getMonitor());
		poDummyRemoteSmartObject->callAsync( poConnectionHandler.get(), poCallRequest.get(), NULL );
		poDummyRemoteSmartObject->waitForAnyEvent(oLock);
		oLock.unlock();

		BOOST_REQUIRE ( poDummyRemoteSmartObject->getLastResult() == "Hello" );
	}
}

BOOST_AUTO_TEST_CASE ( TestDummySyncSkeletonPerfTest ) {
	boost::intrusive_ptr<ISmartBus> poBus (new SmartBus(), false);
	boost::intrusive_ptr<URI> poServerURI ( new URI("tcp://localhost:3333"), false );
	boost::intrusive_ptr<IEndpointLocation> poServerEndpointLocation (
			new EndpointLocation( poBus.get(), poServerURI.get() ), false
	);

	boost::intrusive_ptr<DummySyncSkeleton> poDummySkeleton (new DummySyncSkeleton(), false);
	poDummySkeleton->publish( poBus.get(), poServerEndpointLocation.get() );

	boost::intrusive_ptr<DummyRemoteSmartObject> poDummyRemoteSmartObject ( new DummyRemoteSmartObject(), false );
	poDummyRemoteSmartObject->resolve(poBus.get(), poServerEndpointLocation.get());

	const unsigned int PRF_ITER_SYNC_CNT = 100000;
	time_t nStartTime, nEndTime, nProgressTime1, nProgressTime2;
	nStartTime = time(NULL);
	nProgressTime1 = nStartTime;
	nProgressTime2 = nStartTime;

	unsigned long nTps = 0,	nMaxTps = 0, nMinTps = (unsigned long)-1, nMidTps = 0, nMeasureIter = 0;

	for(unsigned int i=0; i<PRF_ITER_SYNC_CNT; i++, nTps++) {
		boost::intrusive_ptr<CallInterfaceOperationRequest> poCallRequest (
				poDummyRemoteSmartObject->createCallRequest("DummyOperation",NULL), false );

		poCallRequest->getDataTypesCoder()->encodeString("Hello", poCallRequest->getBuffer());

		boost::intrusive_ptr<ConnectionHandler> poConnectionHandler ( poDummyRemoteSmartObject->getConnectionHandlerToSend(), false );
		boost::intrusive_ptr<CallInterfaceOperationResponse> poCallResponse ( poDummyRemoteSmartObject->call( poConnectionHandler.get(), poCallRequest.get() ), false );

		std::string sHelloResult;
		poCallResponse->getDataTypesCoder()->decodeString(sHelloResult, poCallResponse->getBuffer());
		BOOST_REQUIRE ( sHelloResult == "Hello" );

		nProgressTime1 = time(NULL);

		if(nProgressTime1 > nProgressTime2 + 1) {

			nProgressTime2 = nProgressTime1;

			if(nMaxTps < nTps) {
				nMaxTps = nTps;
			}

			if(i > PRF_ITER_SYNC_CNT / 6) {
				if(nMinTps > nTps) {
					nMinTps = nTps;
				}
				nMidTps+=nTps;
				nMeasureIter++;
				BOOST_MESSAGE ( (boost::format("TPS: %1%") %nTps).str() );
			}
			else
				BOOST_MESSAGE ( (boost::format("TPS: %1% (Ignored)") %nTps).str() );
			nTps = 0;
		}
	}

	nEndTime = time(NULL);
	if(nMeasureIter)
		nMidTps = (unsigned long) (nMidTps/(nMeasureIter) );
	BOOST_MESSAGE ((
		boost::format(
			"Max TPS: %1%, MinTps: %2%, MidTps: %3% (%4%)"
		)
			%nMaxTps
			%nMinTps
			%nMidTps
			%(( nMaxTps + nMinTps ) /2)
		).str()
	);
		poDummyRemoteSmartObject.reset();
	poDummySkeleton.reset();
	poServerEndpointLocation.reset();
	poServerURI.reset();
	poBus.reset();
}
